home *** CD-ROM | disk | FTP | other *** search
/ PC go! 2017 October / PCgo 10-2017 CD-ROM Germany.iso / nw.pak / Unnamed File 004863.txt < prev    next >
Encoding:
Text File  |  2015-07-29  |  41.8 KB  |  1,305 lines

  1. // Copyright 2013 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5. var media = {};
  6.  
  7. // Copyright 2013 The Chromium Authors. All rights reserved.
  8. // Use of this source code is governed by a BSD-style license that can be
  9. // found in the LICENSE file.
  10.  
  11. /**
  12.  * A global object that gets used by the C++ interface.
  13.  */
  14. var media = (function() {
  15.   'use strict';
  16.  
  17.   var manager = null;
  18.  
  19.   // A number->string mapping that is populated through the backend that
  20.   // describes the phase that the network entity is in.
  21.   var eventPhases = {};
  22.  
  23.   // A number->string mapping that is populated through the backend that
  24.   // describes the type of event sent from the network.
  25.   var eventTypes = {};
  26.  
  27.   // A mapping of number->CacheEntry where the number is a unique id for that
  28.   // network request.
  29.   var cacheEntries = {};
  30.  
  31.   // A mapping of url->CacheEntity where the url is the url of the resource.
  32.   var cacheEntriesByKey = {};
  33.  
  34.   var requrestURLs = {};
  35.  
  36.   var media = {
  37.     BAR_WIDTH: 200,
  38.     BAR_HEIGHT: 25
  39.   };
  40.  
  41.   /**
  42.    * Users of |media| must call initialize prior to calling other methods.
  43.    */
  44.   media.initialize = function(theManager) {
  45.     manager = theManager;
  46.   };
  47.  
  48.   media.onReceiveAudioStreamData = function(audioStreamData) {
  49.     for (var component in audioStreamData) {
  50.       media.updateAudioComponent(audioStreamData[component]);
  51.     }
  52.   };
  53.  
  54.   media.onReceiveVideoCaptureCapabilities = function(videoCaptureCapabilities) {
  55.     manager.updateVideoCaptureCapabilities(videoCaptureCapabilities)
  56.   }
  57.  
  58.   media.onReceiveConstants = function(constants) {
  59.     for (var key in constants.eventTypes) {
  60.       var value = constants.eventTypes[key];
  61.       eventTypes[value] = key;
  62.     }
  63.  
  64.     for (var key in constants.eventPhases) {
  65.       var value = constants.eventPhases[key];
  66.       eventPhases[value] = key;
  67.     }
  68.   };
  69.  
  70.   media.cacheForUrl = function(url) {
  71.     return cacheEntriesByKey[url];
  72.   };
  73.  
  74.   media.onNetUpdate = function(updates) {
  75.     updates.forEach(function(update) {
  76.       var id = update.source.id;
  77.       if (!cacheEntries[id])
  78.         cacheEntries[id] = new media.CacheEntry;
  79.  
  80.       switch (eventPhases[update.phase] + '.' + eventTypes[update.type]) {
  81.         case 'PHASE_BEGIN.DISK_CACHE_ENTRY_IMPL':
  82.           var key = update.params.key;
  83.  
  84.           // Merge this source with anything we already know about this key.
  85.           if (cacheEntriesByKey[key]) {
  86.             cacheEntriesByKey[key].merge(cacheEntries[id]);
  87.             cacheEntries[id] = cacheEntriesByKey[key];
  88.           } else {
  89.             cacheEntriesByKey[key] = cacheEntries[id];
  90.           }
  91.           cacheEntriesByKey[key].key = key;
  92.           break;
  93.  
  94.         case 'PHASE_BEGIN.SPARSE_READ':
  95.           cacheEntries[id].readBytes(update.params.offset,
  96.                                       update.params.buff_len);
  97.           cacheEntries[id].sparse = true;
  98.           break;
  99.  
  100.         case 'PHASE_BEGIN.SPARSE_WRITE':
  101.           cacheEntries[id].writeBytes(update.params.offset,
  102.                                        update.params.buff_len);
  103.           cacheEntries[id].sparse = true;
  104.           break;
  105.  
  106.         case 'PHASE_BEGIN.URL_REQUEST_START_JOB':
  107.           requrestURLs[update.source.id] = update.params.url;
  108.           break;
  109.  
  110.         case 'PHASE_NONE.HTTP_TRANSACTION_READ_RESPONSE_HEADERS':
  111.           // Record the total size of the file if this was a range request.
  112.           var range = /content-range:\s*bytes\s*\d+-\d+\/(\d+)/i.exec(
  113.               update.params.headers);
  114.           var key = requrestURLs[update.source.id];
  115.           delete requrestURLs[update.source.id];
  116.           if (range && key) {
  117.             if (!cacheEntriesByKey[key]) {
  118.               cacheEntriesByKey[key] = new media.CacheEntry;
  119.               cacheEntriesByKey[key].key = key;
  120.             }
  121.             cacheEntriesByKey[key].size = range[1];
  122.           }
  123.           break;
  124.       }
  125.     });
  126.   };
  127.  
  128.   media.onRendererTerminated = function(renderId) {
  129.     util.object.forEach(manager.players_, function(playerInfo, id) {
  130.       if (playerInfo.properties['render_id'] == renderId) {
  131.         manager.removePlayer(id);
  132.       }
  133.     });
  134.   };
  135.  
  136.   media.updateAudioComponent = function(component) {
  137.     var uniqueComponentId = component.owner_id + ':' + component.component_id;
  138.     switch (component.status) {
  139.       case 'closed':
  140.         manager.removeAudioComponent(
  141.             component.component_type, uniqueComponentId);
  142.         break;
  143.       default:
  144.         manager.updateAudioComponent(
  145.             component.component_type, uniqueComponentId, component);
  146.         break;
  147.     }
  148.   };
  149.  
  150.   media.onPlayerOpen = function(id, timestamp) {
  151.     manager.addPlayer(id, timestamp);
  152.   };
  153.  
  154.   media.onMediaEvent = function(event) {
  155.     var source = event.renderer + ':' + event.player;
  156.  
  157.     // Although this gets called on every event, there is nothing we can do
  158.     // because there is no onOpen event.
  159.     media.onPlayerOpen(source);
  160.     manager.updatePlayerInfoNoRecord(
  161.         source, event.ticksMillis, 'render_id', event.renderer);
  162.     manager.updatePlayerInfoNoRecord(
  163.         source, event.ticksMillis, 'player_id', event.player);
  164.  
  165.     var propertyCount = 0;
  166.     util.object.forEach(event.params, function(value, key) {
  167.       key = key.trim();
  168.  
  169.       // These keys get spammed *a lot*, so put them on the display
  170.       // but don't log list.
  171.       if (key === 'buffer_start' ||
  172.           key === 'buffer_end' ||
  173.           key === 'buffer_current' ||
  174.           key === 'is_downloading_data') {
  175.         manager.updatePlayerInfoNoRecord(
  176.             source, event.ticksMillis, key, value);
  177.       } else {
  178.         manager.updatePlayerInfo(source, event.ticksMillis, key, value);
  179.       }
  180.       propertyCount += 1;
  181.     });
  182.  
  183.     if (propertyCount === 0) {
  184.       manager.updatePlayerInfo(
  185.           source, event.ticksMillis, 'EVENT', event.type);
  186.     }
  187.   };
  188.  
  189.   // |chrome| is not defined during tests.
  190.   if (window.chrome && window.chrome.send) {
  191.     chrome.send('getEverything');
  192.   }
  193.   return media;
  194. }());
  195.  
  196. // Copyright 2013 The Chromium Authors. All rights reserved.
  197. // Use of this source code is governed by a BSD-style license that can be
  198. // found in the LICENSE file.
  199.  
  200. /**
  201.  * @fileoverview Some utility functions that don't belong anywhere else in the
  202.  * code.
  203.  */
  204.  
  205. var util = (function() {
  206.   var util = {};
  207.   util.object = {};
  208.   /**
  209.    * Calls a function for each element in an object/map/hash.
  210.    *
  211.    * @param obj The object to iterate over.
  212.    * @param f The function to call on every value in the object.  F should have
  213.    * the following arguments: f(value, key, object) where value is the value
  214.    * of the property, key is the corresponding key, and obj is the object that
  215.    * was passed in originally.
  216.    * @param optObj The object use as 'this' within f.
  217.    */
  218.   util.object.forEach = function(obj, f, optObj) {
  219.     'use strict';
  220.     var key;
  221.     for (key in obj) {
  222.       if (obj.hasOwnProperty(key)) {
  223.         f.call(optObj, obj[key], key, obj);
  224.       }
  225.     }
  226.   };
  227.   util.millisecondsToString = function(timeMillis) {
  228.     function pad(num) {
  229.       num = num.toString();
  230.       if (num.length < 2) {
  231.         return '0' + num;
  232.       }
  233.       return num;
  234.     }
  235.  
  236.     var date = new Date(timeMillis);
  237.     return pad(date.getUTCHours()) + ':' + pad(date.getUTCMinutes()) + ':' +
  238.         pad(date.getUTCSeconds()) + ' ' + pad((date.getMilliseconds()) % 1000);
  239.   };
  240.  
  241.   return util;
  242. }());
  243.  
  244. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  245. // Use of this source code is governed by a BSD-style license that can be
  246. // found in the LICENSE file.
  247.  
  248. cr.define('media', function() {
  249.   'use strict';
  250.  
  251.   /**
  252.    * This class represents a file cached by net.
  253.    */
  254.   function CacheEntry() {
  255.     this.read_ = new media.DisjointRangeSet;
  256.     this.written_ = new media.DisjointRangeSet;
  257.     this.available_ = new media.DisjointRangeSet;
  258.  
  259.     // Set to true when we know the entry is sparse.
  260.     this.sparse = false;
  261.     this.key = null;
  262.     this.size = null;
  263.  
  264.     // The <details> element representing this CacheEntry.
  265.     this.details_ = document.createElement('details');
  266.     this.details_.className = 'cache-entry';
  267.     this.details_.open = false;
  268.  
  269.     // The <details> summary line. It contains a chart of requested file ranges
  270.     // and the url if we know it.
  271.     var summary = document.createElement('summary');
  272.  
  273.     this.summaryText_ = document.createTextNode('');
  274.     summary.appendChild(this.summaryText_);
  275.  
  276.     summary.appendChild(document.createTextNode(' '));
  277.  
  278.     // Controls to modify this CacheEntry.
  279.     var controls = document.createElement('span');
  280.     controls.className = 'cache-entry-controls';
  281.     summary.appendChild(controls);
  282.     summary.appendChild(document.createElement('br'));
  283.  
  284.     // A link to clear recorded data from this CacheEntry.
  285.     var clearControl = document.createElement('a');
  286.     clearControl.href = 'javascript:void(0)';
  287.     clearControl.onclick = this.clear.bind(this);
  288.     clearControl.textContent = '(clear entry)';
  289.     controls.appendChild(clearControl);
  290.  
  291.     this.details_.appendChild(summary);
  292.  
  293.     // The canvas for drawing cache writes.
  294.     this.writeCanvas = document.createElement('canvas');
  295.     this.writeCanvas.width = media.BAR_WIDTH;
  296.     this.writeCanvas.height = media.BAR_HEIGHT;
  297.     this.details_.appendChild(this.writeCanvas);
  298.  
  299.     // The canvas for drawing cache reads.
  300.     this.readCanvas = document.createElement('canvas');
  301.     this.readCanvas.width = media.BAR_WIDTH;
  302.     this.readCanvas.height = media.BAR_HEIGHT;
  303.     this.details_.appendChild(this.readCanvas);
  304.  
  305.     // A tabular representation of the data in the above canvas.
  306.     this.detailTable_ = document.createElement('table');
  307.     this.detailTable_.className = 'cache-table';
  308.     this.details_.appendChild(this.detailTable_);
  309.   }
  310.  
  311.   CacheEntry.prototype = {
  312.     /**
  313.      * Mark a range of bytes as read from the cache.
  314.      * @param {int} start The first byte read.
  315.      * @param {int} length The number of bytes read.
  316.      */
  317.     readBytes: function(start, length) {
  318.       start = parseInt(start);
  319.       length = parseInt(length);
  320.       this.read_.add(start, start + length);
  321.       this.available_.add(start, start + length);
  322.       this.sparse = true;
  323.     },
  324.  
  325.     /**
  326.      * Mark a range of bytes as written to the cache.
  327.      * @param {int} start The first byte written.
  328.      * @param {int} length The number of bytes written.
  329.      */
  330.     writeBytes: function(start, length) {
  331.       start = parseInt(start);
  332.       length = parseInt(length);
  333.       this.written_.add(start, start + length);
  334.       this.available_.add(start, start + length);
  335.       this.sparse = true;
  336.     },
  337.  
  338.     /**
  339.      * Merge this CacheEntry with another, merging recorded ranges and flags.
  340.      * @param {CacheEntry} other The CacheEntry to merge into this one.
  341.      */
  342.     merge: function(other) {
  343.       this.read_.merge(other.read_);
  344.       this.written_.merge(other.written_);
  345.       this.available_.merge(other.available_);
  346.       this.sparse = this.sparse || other.sparse;
  347.       this.key = this.key || other.key;
  348.       this.size = this.size || other.size;
  349.     },
  350.  
  351.     /**
  352.      * Clear all recorded ranges from this CacheEntry and redraw this.details_.
  353.      */
  354.     clear: function() {
  355.       this.read_ = new media.DisjointRangeSet;
  356.       this.written_ = new media.DisjointRangeSet;
  357.       this.available_ = new media.DisjointRangeSet;
  358.       this.generateDetails();
  359.     },
  360.  
  361.     /**
  362.      * Helper for drawCacheReadsToCanvas() and drawCacheWritesToCanvas().
  363.      *
  364.      * Accepts the entries to draw, a canvas fill style, and the canvas to
  365.      * draw on.
  366.      */
  367.     drawCacheEntriesToCanvas: function(entries, fillStyle, canvas) {
  368.       // Don't bother drawing anything if we don't know the total size.
  369.       if (!this.size) {
  370.         return;
  371.       }
  372.  
  373.       var width = canvas.width;
  374.       var height = canvas.height;
  375.       var context = canvas.getContext('2d');
  376.       var fileSize = this.size;
  377.  
  378.       context.fillStyle = '#aaa';
  379.       context.fillRect(0, 0, width, height);
  380.  
  381.       function drawRange(start, end) {
  382.         var left = start / fileSize * width;
  383.         var right = end / fileSize * width;
  384.         context.fillRect(left, 0, right - left, height);
  385.       }
  386.  
  387.       context.fillStyle = fillStyle;
  388.       entries.map(function(start, end) {
  389.         drawRange(start, end);
  390.       });
  391.     },
  392.  
  393.     /**
  394.      * Draw cache writes to the given canvas.
  395.      *
  396.      * It should consist of a horizontal bar with highlighted sections to
  397.      * represent which parts of a file have been written to the cache.
  398.      *
  399.      * e.g. |xxxxxx----------x|
  400.      */
  401.     drawCacheWritesToCanvas: function(canvas) {
  402.       this.drawCacheEntriesToCanvas(this.written_, '#00a', canvas);
  403.     },
  404.  
  405.     /**
  406.      * Draw cache reads to the given canvas.
  407.      *
  408.      * It should consist of a horizontal bar with highlighted sections to
  409.      * represent which parts of a file have been read from the cache.
  410.      *
  411.      * e.g. |xxxxxx----------x|
  412.      */
  413.     drawCacheReadsToCanvas: function(canvas) {
  414.       this.drawCacheEntriesToCanvas(this.read_, '#0a0', canvas);
  415.     },
  416.  
  417.     /**
  418.      * Update this.details_ to contain everything we currently know about
  419.      * this file.
  420.      */
  421.     generateDetails: function() {
  422.       function makeElement(tag, content) {
  423.         var toReturn = document.createElement(tag);
  424.         toReturn.textContent = content;
  425.         return toReturn;
  426.       }
  427.  
  428.       this.details_.id = this.key;
  429.       this.summaryText_.textContent = this.key || 'Unknown File';
  430.  
  431.       this.detailTable_.textContent = '';
  432.       var header = document.createElement('thead');
  433.       var footer = document.createElement('tfoot');
  434.       var body = document.createElement('tbody');
  435.       this.detailTable_.appendChild(header);
  436.       this.detailTable_.appendChild(footer);
  437.       this.detailTable_.appendChild(body);
  438.  
  439.       var headerRow = document.createElement('tr');
  440.       headerRow.appendChild(makeElement('th', 'Read From Cache'));
  441.       headerRow.appendChild(makeElement('th', 'Written To Cache'));
  442.       header.appendChild(headerRow);
  443.  
  444.       var footerRow = document.createElement('tr');
  445.       var footerCell = document.createElement('td');
  446.       footerCell.textContent = 'Out of ' + (this.size || 'unkown size');
  447.       footerCell.setAttribute('colspan', 2);
  448.       footerRow.appendChild(footerCell);
  449.       footer.appendChild(footerRow);
  450.  
  451.       var read = this.read_.map(function(start, end) {
  452.         return start + ' - ' + end;
  453.       });
  454.       var written = this.written_.map(function(start, end) {
  455.         return start + ' - ' + end;
  456.       });
  457.  
  458.       var length = Math.max(read.length, written.length);
  459.       for (var i = 0; i < length; i++) {
  460.         var row = document.createElement('tr');
  461.         row.appendChild(makeElement('td', read[i] || ''));
  462.         row.appendChild(makeElement('td', written[i] || ''));
  463.         body.appendChild(row);
  464.       }
  465.  
  466.       this.drawCacheWritesToCanvas(this.writeCanvas);
  467.       this.drawCacheReadsToCanvas(this.readCanvas);
  468.     },
  469.  
  470.     /**
  471.      * Render this CacheEntry as a <li>.
  472.      * @return {HTMLElement} A <li> representing this CacheEntry.
  473.      */
  474.     toListItem: function() {
  475.       this.generateDetails();
  476.  
  477.       var result = document.createElement('li');
  478.       result.appendChild(this.details_);
  479.       return result;
  480.     }
  481.   };
  482.  
  483.   return {
  484.     CacheEntry: CacheEntry
  485.   };
  486. });
  487.  
  488. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  489. // Use of this source code is governed by a BSD-style license that can be
  490. // found in the LICENSE file.
  491.  
  492. cr.define('media', function() {
  493.  
  494.   /**
  495.    * This class represents a collection of non-intersecting ranges. Ranges
  496.    * specified by (start, end) can be added and removed at will. It is used to
  497.    * record which sections of a media file have been cached, e.g. the first and
  498.    * last few kB plus several MB in the middle.
  499.    *
  500.    * Example usage:
  501.    * someRange.add(0, 100);     // Contains 0-100.
  502.    * someRange.add(150, 200);   // Contains 0-100, 150-200.
  503.    * someRange.remove(25, 75);  // Contains 0-24, 76-100, 150-200.
  504.    * someRange.add(25, 149);    // Contains 0-200.
  505.    */
  506.   function DisjointRangeSet() {
  507.     this.ranges_ = {};
  508.   }
  509.  
  510.   DisjointRangeSet.prototype = {
  511.     /**
  512.      * Deletes all ranges intersecting with (start ... end) and returns the
  513.      * extents of the cleared area.
  514.      * @param {int} start The start of the range to remove.
  515.      * @param {int} end The end of the range to remove.
  516.      * @param {int} sloppiness 0 removes only strictly overlapping ranges, and
  517.      *                         1 removes adjacent ones.
  518.      * @return {Object} The start and end of the newly cleared range.
  519.      */
  520.     clearRange: function(start, end, sloppiness) {
  521.       var ranges = this.ranges_;
  522.       var result = {start: start, end: end};
  523.  
  524.       for (var rangeStart in this.ranges_) {
  525.         rangeEnd = this.ranges_[rangeStart];
  526.         // A range intersects another if its start lies within the other range
  527.         // or vice versa.
  528.         if ((rangeStart >= start && rangeStart <= (end + sloppiness)) ||
  529.             (start >= rangeStart && start <= (rangeEnd + sloppiness))) {
  530.           delete ranges[rangeStart];
  531.           result.start = Math.min(result.start, rangeStart);
  532.           result.end = Math.max(result.end, rangeEnd);
  533.         }
  534.       }
  535.  
  536.       return result;
  537.     },
  538.  
  539.     /**
  540.      * Adds a range to this DisjointRangeSet.
  541.      * Joins adjacent and overlapping ranges together.
  542.      * @param {int} start The beginning of the range to add, inclusive.
  543.      * @param {int} end The end of the range to add, inclusive.
  544.      */
  545.     add: function(start, end) {
  546.       if (end < start)
  547.         return;
  548.  
  549.       // Remove all touching ranges.
  550.       result = this.clearRange(start, end, 1);
  551.       // Add back a single contiguous range.
  552.       this.ranges_[Math.min(start, result.start)] = Math.max(end, result.end);
  553.     },
  554.  
  555.     /**
  556.      * Combines a DisjointRangeSet with this one.
  557.      * @param {DisjointRangeSet} ranges A DisjointRangeSet to be squished into
  558.      * this one.
  559.      */
  560.     merge: function(other) {
  561.       var ranges = this;
  562.       other.forEach(function(start, end) { ranges.add(start, end); });
  563.     },
  564.  
  565.     /**
  566.      * Removes a range from this DisjointRangeSet.
  567.      * Will split existing ranges if necessary.
  568.      * @param {int} start The beginning of the range to remove, inclusive.
  569.      * @param {int} end The end of the range to remove, inclusive.
  570.      */
  571.     remove: function(start, end) {
  572.       if (end < start)
  573.         return;
  574.  
  575.       // Remove instersecting ranges.
  576.       result = this.clearRange(start, end, 0);
  577.  
  578.       // Add back non-overlapping ranges.
  579.       if (result.start < start)
  580.         this.ranges_[result.start] = start - 1;
  581.       if (result.end > end)
  582.         this.ranges_[end + 1] = result.end;
  583.     },
  584.  
  585.     /**
  586.      * Iterates over every contiguous range in this DisjointRangeSet, calling a
  587.      * function for each (start, end).
  588.      * @param {function(int, int)} iterator The function to call on each range.
  589.      */
  590.     forEach: function(iterator) {
  591.       for (var start in this.ranges_)
  592.         iterator(start, this.ranges_[start]);
  593.     },
  594.  
  595.     /**
  596.      * Maps this DisjointRangeSet to an array by calling a given function on the
  597.      * start and end of each contiguous range, sorted by start.
  598.      * @param {function(int, int)} mapper Maps a range to an array element.
  599.      * @return {Array} An array of each mapper(range).
  600.      */
  601.     map: function(mapper) {
  602.       var starts = [];
  603.       for (var start in this.ranges_)
  604.         starts.push(parseInt(start));
  605.       starts.sort(function(a, b) {
  606.         return a - b;
  607.       });
  608.  
  609.       var ranges = this.ranges_;
  610.       var results = starts.map(function(s) {
  611.         return mapper(s, ranges[s]);
  612.       });
  613.  
  614.       return results;
  615.     },
  616.  
  617.     /**
  618.      * Finds the maximum value present in any of the contained ranges.
  619.      * @return {int} The maximum value contained by this DisjointRangeSet.
  620.      */
  621.     max: function() {
  622.       var max = -Infinity;
  623.       for (var start in this.ranges_)
  624.         max = Math.max(max, this.ranges_[start]);
  625.       return max;
  626.     },
  627.   };
  628.  
  629.   return {
  630.     DisjointRangeSet: DisjointRangeSet
  631.   };
  632. });
  633.  
  634. // Copyright 2013 The Chromium Authors. All rights reserved.
  635. // Use of this source code is governed by a BSD-style license that can be
  636. // found in the LICENSE file.
  637.  
  638. /**
  639.  * @fileoverview A class for keeping track of the details of a player.
  640.  */
  641.  
  642. var PlayerInfo = (function() {
  643.   'use strict';
  644.  
  645.   /**
  646.    * A class that keeps track of properties on a media player.
  647.    * @param id A unique id that can be used to identify this player.
  648.    */
  649.   function PlayerInfo(id) {
  650.     this.id = id;
  651.     // The current value of the properties for this player.
  652.     this.properties = {};
  653.     // All of the past (and present) values of the properties.
  654.     this.pastValues = {};
  655.  
  656.     // Every single event in the order in which they were received.
  657.     this.allEvents = [];
  658.     this.lastRendered = 0;
  659.  
  660.     this.firstTimestamp_ = -1;
  661.   }
  662.  
  663.   PlayerInfo.prototype = {
  664.     /**
  665.      * Adds or set a property on this player.
  666.      * This is the default logging method as it keeps track of old values.
  667.      * @param timestamp  The time in milliseconds since the Epoch.
  668.      * @param key A String key that describes the property.
  669.      * @param value The value of the property.
  670.      */
  671.     addProperty: function(timestamp, key, value) {
  672.       // The first timestamp that we get will be recorded.
  673.       // Then, all future timestamps are deltas of that.
  674.       if (this.firstTimestamp_ === -1) {
  675.         this.firstTimestamp_ = timestamp;
  676.       }
  677.  
  678.       if (typeof key !== 'string') {
  679.         throw new Error(typeof key + ' is not a valid key type');
  680.       }
  681.  
  682.       this.properties[key] = value;
  683.  
  684.       if (!this.pastValues[key]) {
  685.         this.pastValues[key] = [];
  686.       }
  687.  
  688.       var recordValue = {
  689.         time: timestamp - this.firstTimestamp_,
  690.         key: key,
  691.         value: value
  692.       };
  693.  
  694.       this.pastValues[key].push(recordValue);
  695.       this.allEvents.push(recordValue);
  696.     },
  697.  
  698.     /**
  699.      * Adds or set a property on this player.
  700.      * Does not keep track of old values.  This is better for
  701.      * values that get spammed repeatedly.
  702.      * @param timestamp The time in milliseconds since the Epoch.
  703.      * @param key A String key that describes the property.
  704.      * @param value The value of the property.
  705.      */
  706.     addPropertyNoRecord: function(timestamp, key, value) {
  707.       this.addProperty(timestamp, key, value);
  708.       this.allEvents.pop();
  709.     }
  710.   };
  711.  
  712.   return PlayerInfo;
  713. }());
  714.  
  715. // Copyright 2013 The Chromium Authors. All rights reserved.
  716. // Use of this source code is governed by a BSD-style license that can be
  717. // found in the LICENSE file.
  718.  
  719. /**
  720.  * @fileoverview Keeps track of all the existing PlayerInfo and
  721.  * audio stream objects and is the entry-point for messages from the backend.
  722.  *
  723.  * The events captured by Manager (add, remove, update) are relayed
  724.  * to the clientRenderer which it can choose to use to modify the UI.
  725.  */
  726. var Manager = (function() {
  727.   'use strict';
  728.  
  729.   function Manager(clientRenderer) {
  730.     this.players_ = {};
  731.     this.audioComponents_ = [];
  732.     this.clientRenderer_ = clientRenderer;
  733.   }
  734.  
  735.   Manager.prototype = {
  736.     /**
  737.      * Updates an audio-component.
  738.      * @param componentType Integer AudioComponent enum value; must match values
  739.      * from the AudioLogFactory::AudioComponent enum.
  740.      * @param componentId The unique-id of the audio-component.
  741.      * @param componentData The actual component data dictionary.
  742.      */
  743.     updateAudioComponent: function(componentType, componentId, componentData) {
  744.       if (!(componentType in this.audioComponents_))
  745.         this.audioComponents_[componentType] = {};
  746.       if (!(componentId in this.audioComponents_[componentType])) {
  747.         this.audioComponents_[componentType][componentId] = componentData;
  748.       } else {
  749.         for (var key in componentData) {
  750.           this.audioComponents_[componentType][componentId][key] =
  751.               componentData[key];
  752.         }
  753.       }
  754.       this.clientRenderer_.audioComponentAdded(
  755.           componentType, this.audioComponents_[componentType]);
  756.     },
  757.  
  758.     /**
  759.      * Removes an audio-stream from the manager.
  760.      * @param id The unique-id of the audio-stream.
  761.      */
  762.     removeAudioComponent: function(componentType, componentId) {
  763.       if (!(componentType in this.audioComponents_) ||
  764.           !(componentId in this.audioComponents_[componentType])) {
  765.         return;
  766.       }
  767.  
  768.       delete this.audioComponents_[componentType][componentId];
  769.       this.clientRenderer_.audioComponentRemoved(
  770.           componentType, this.audioComponents_[componentType]);
  771.     },
  772.  
  773.     /**
  774.      * Adds a player to the list of players to manage.
  775.      */
  776.     addPlayer: function(id) {
  777.       if (this.players_[id]) {
  778.         return;
  779.       }
  780.       // Make the PlayerProperty and add it to the mapping
  781.       this.players_[id] = new PlayerInfo(id);
  782.       this.clientRenderer_.playerAdded(this.players_, this.players_[id]);
  783.     },
  784.  
  785.     /**
  786.      * Attempts to remove a player from the UI.
  787.      * @param id The ID of the player to remove.
  788.      */
  789.     removePlayer: function(id) {
  790.       delete this.players_[id];
  791.       this.clientRenderer_.playerRemoved(this.players_, this.players_[id]);
  792.     },
  793.  
  794.     updatePlayerInfoNoRecord: function(id, timestamp, key, value) {
  795.       if (!this.players_[id]) {
  796.         console.error('[updatePlayerInfo] Id ' + id + ' does not exist');
  797.         return;
  798.       }
  799.  
  800.       this.players_[id].addPropertyNoRecord(timestamp, key, value);
  801.       this.clientRenderer_.playerUpdated(this.players_,
  802.                                          this.players_[id],
  803.                                          key,
  804.                                          value);
  805.     },
  806.  
  807.     /**
  808.      *
  809.      * @param id The unique ID that identifies the player to be updated.
  810.      * @param timestamp The timestamp of when the change occured.  This
  811.      * timestamp is *not* normalized.
  812.      * @param key The name of the property to be added/changed.
  813.      * @param value The value of the property.
  814.      */
  815.     updatePlayerInfo: function(id, timestamp, key, value) {
  816.       if (!this.players_[id]) {
  817.         console.error('[updatePlayerInfo] Id ' + id + ' does not exist');
  818.         return;
  819.       }
  820.  
  821.       this.players_[id].addProperty(timestamp, key, value);
  822.       this.clientRenderer_.playerUpdated(this.players_,
  823.                                          this.players_[id],
  824.                                          key,
  825.                                          value);
  826.     },
  827.  
  828.     parseVideoCaptureFormat_: function(format) {
  829.       /**
  830.        * Example:
  831.        *
  832.        * format:
  833.        *   "resolution: 1280x720, fps: 30.000000, pixel format: I420"
  834.        *
  835.        * formatDict:
  836.        *   {'resolution':'1280x720', 'fps': '30.00'}
  837.        */
  838.       var parts = format.split(', ');
  839.       var formatDict = {};
  840.       for (var i in parts) {
  841.         var kv = parts[i].split(': ');
  842.         formatDict[kv[0]] = kv[1];
  843.       }
  844.  
  845.       // Round down the FPS to 2 decimals.
  846.       formatDict['fps'] = parseFloat(formatDict['fps']).toFixed(2);
  847.  
  848.       // The camera does not actually output I420 so this info is misleading.
  849.       delete formatDict['pixel format'];
  850.  
  851.       return formatDict;
  852.     },
  853.  
  854.     updateVideoCaptureCapabilities: function(videoCaptureCapabilities) {
  855.       // Parse the video formats to be structured for the table.
  856.       for (var i in videoCaptureCapabilities) {
  857.         for (var j in videoCaptureCapabilities[i]['formats']) {
  858.           videoCaptureCapabilities[i]['formats'][j] =
  859.               this.parseVideoCaptureFormat_(
  860.                     videoCaptureCapabilities[i]['formats'][j]);
  861.         }
  862.       }
  863.  
  864.       // The keys of each device to be shown in order of appearance.
  865.       var videoCaptureDeviceKeys = ['name','formats','captureApi','id'];
  866.  
  867.       this.clientRenderer_.redrawVideoCaptureCapabilities(
  868.           videoCaptureCapabilities, videoCaptureDeviceKeys);
  869.     }
  870.   };
  871.  
  872.   return Manager;
  873. }());
  874.  
  875. // Copyright 2013 The Chromium Authors. All rights reserved.
  876. // Use of this source code is governed by a BSD-style license that can be
  877. // found in the LICENSE file.
  878.  
  879. var ClientRenderer = (function() {
  880.   var ClientRenderer = function() {
  881.     this.playerListElement = document.getElementById('player-list');
  882.     this.propertiesTable =
  883.         document.getElementById('property-table').querySelector('tbody');
  884.     this.logTable = document.getElementById('log').querySelector('tbody');
  885.     this.graphElement = document.getElementById('graphs');
  886.     this.propertyName = document.getElementById('property-name');
  887.  
  888.     this.selectedPlayer = null;
  889.     this.selectedAudioComponentType = null;
  890.     this.selectedAudioComponentId = null;
  891.     this.selectedAudioCompontentData = null;
  892.  
  893.     this.selectedPlayerLogIndex = 0;
  894.  
  895.     this.filterFunction = function() { return true; };
  896.     this.filterText = document.getElementById('filter-text');
  897.     this.filterText.onkeyup = this.onTextChange_.bind(this);
  898.  
  899.     this.bufferCanvas = document.createElement('canvas');
  900.     this.bufferCanvas.width = media.BAR_WIDTH;
  901.     this.bufferCanvas.height = media.BAR_HEIGHT;
  902.  
  903.     this.clipboardTextarea = document.getElementById('clipboard-textarea');
  904.     this.clipboardButton = document.getElementById('copy-button');
  905.     this.clipboardButton.onclick = this.copyToClipboard_.bind(this);
  906.  
  907.     this.hiddenKeys = ['component_id', 'component_type', 'owner_id'];
  908.   };
  909.  
  910.   function removeChildren(element) {
  911.     while (element.hasChildNodes()) {
  912.       element.removeChild(element.lastChild);
  913.     }
  914.   };
  915.  
  916.   function createButton(text, select_cb) {
  917.     var button = document.createElement('button');
  918.  
  919.     button.appendChild(document.createTextNode(text));
  920.     button.onclick = function() {
  921.       select_cb();
  922.     };
  923.  
  924.     return button;
  925.   };
  926.  
  927.   ClientRenderer.prototype = {
  928.     /**
  929.      * Called when an audio component is added to the collection.
  930.      * @param componentType Integer AudioComponent enum value; must match values
  931.      * from the AudioLogFactory::AudioComponent enum.
  932.      * @param components The entire map of components (name -> dict).
  933.      */
  934.     audioComponentAdded: function(componentType, components) {
  935.       this.redrawAudioComponentList_(componentType, components);
  936.  
  937.       // Redraw the component if it's currently selected.
  938.       if (this.selectedAudioComponentType == componentType &&
  939.           this.selectedAudioComponentId &&
  940.           this.selectedAudioComponentId in components) {
  941.         this.selectAudioComponent_(
  942.             componentType, this.selectedAudioComponentId,
  943.             components[this.selectedAudioComponentId]);
  944.       }
  945.     },
  946.  
  947.     /**
  948.      * Called when an audio component is removed from the collection.
  949.      * @param componentType Integer AudioComponent enum value; must match values
  950.      * from the AudioLogFactory::AudioComponent enum.
  951.      * @param components The entire map of components (name -> dict).
  952.      */
  953.     audioComponentRemoved: function(componentType, components) {
  954.       this.redrawAudioComponentList_(componentType, components);
  955.  
  956.       // Clear the component if it was previously currently selected.
  957.       if (this.selectedAudioComponentType == componentType &&
  958.           !(this.selectedAudioComponentId in components)) {
  959.         this.selectAudioComponent_(null, null, {});
  960.       }
  961.     },
  962.  
  963.     /**
  964.      * Called when a player is added to the collection.
  965.      * @param players The entire map of id -> player.
  966.      * @param player_added The player that is added.
  967.      */
  968.     playerAdded: function(players, playerAdded) {
  969.       this.redrawPlayerList_(players);
  970.     },
  971.  
  972.     /**
  973.      * Called when a playre is removed from the collection.
  974.      * @param players The entire map of id -> player.
  975.      * @param player_added The player that was removed.
  976.      */
  977.     playerRemoved: function(players, playerRemoved) {
  978.       this.redrawPlayerList_(players);
  979.     },
  980.  
  981.     /**
  982.      * Called when a property on a player is changed.
  983.      * @param players The entire map of id -> player.
  984.      * @param player The player that had its property changed.
  985.      * @param key The name of the property that was changed.
  986.      * @param value The new value of the property.
  987.      */
  988.     playerUpdated: function(players, player, key, value) {
  989.       if (player === this.selectedPlayer) {
  990.         this.drawProperties_(player.properties);
  991.         this.drawLog_();
  992.         this.drawGraphs_();
  993.       }
  994.       if (key === 'name' || key === 'url') {
  995.         this.redrawPlayerList_(players);
  996.       }
  997.     },
  998.  
  999.     createVideoCaptureFormatTable: function(formats) {
  1000.       if (!formats || formats.length == 0)
  1001.         return document.createTextNode('No formats');
  1002.  
  1003.       var table = document.createElement('table');
  1004.       var thead = document.createElement('thead');
  1005.       var theadRow = document.createElement('tr');
  1006.       for (var key in formats[0]) {
  1007.         var th = document.createElement('th');
  1008.         th.appendChild(document.createTextNode(key));
  1009.         theadRow.appendChild(th);
  1010.       }
  1011.       thead.appendChild(theadRow);
  1012.       table.appendChild(thead);
  1013.       var tbody = document.createElement('tbody');
  1014.       for (var i=0; i < formats.length; ++i) {
  1015.         var tr = document.createElement('tr')
  1016.         for (var key in formats[i]) {
  1017.           var td = document.createElement('td');
  1018.           td.appendChild(document.createTextNode(formats[i][key]));
  1019.           tr.appendChild(td);
  1020.         }
  1021.         tbody.appendChild(tr);
  1022.       }
  1023.       table.appendChild(tbody);
  1024.       table.classList.add('video-capture-formats-table');
  1025.       return table;
  1026.     },
  1027.  
  1028.     redrawVideoCaptureCapabilities: function(videoCaptureCapabilities, keys) {
  1029.       var copyButtonElement =
  1030.           document.getElementById('video-capture-capabilities-copy-button');
  1031.       copyButtonElement.onclick = function() {
  1032.         window.prompt('Copy to clipboard: Ctrl+C, Enter',
  1033.                       JSON.stringify(videoCaptureCapabilities))
  1034.       }
  1035.  
  1036.       var videoTableBodyElement  =
  1037.           document.getElementById('video-capture-capabilities-tbody');
  1038.       removeChildren(videoTableBodyElement);
  1039.  
  1040.       for (var component in videoCaptureCapabilities) {
  1041.         var tableRow =  document.createElement('tr');
  1042.         var device = videoCaptureCapabilities[ component ];
  1043.         for (var i in keys) {
  1044.           var value = device[keys[i]];
  1045.           var tableCell = document.createElement('td');
  1046.           var cellElement;
  1047.           if ((typeof value) == (typeof [])) {
  1048.             cellElement = this.createVideoCaptureFormatTable(value);
  1049.           } else {
  1050.             cellElement = document.createTextNode(
  1051.                 ((typeof value) == 'undefined') ? 'n/a' : value);
  1052.           }
  1053.           tableCell.appendChild(cellElement)
  1054.           tableRow.appendChild(tableCell);
  1055.         }
  1056.         videoTableBodyElement.appendChild(tableRow);
  1057.       }
  1058.     },
  1059.  
  1060.     redrawAudioComponentList_: function(componentType, components) {
  1061.       function redrawList(renderer, baseName, element) {
  1062.         var fragment = document.createDocumentFragment();
  1063.         for (id in components) {
  1064.           var li = document.createElement('li');
  1065.           var friendlyName = baseName + ' ' + id;
  1066.           li.appendChild(createButton(
  1067.               friendlyName, renderer.selectAudioComponent_.bind(
  1068.                   renderer, componentType, id, components[id], friendlyName)));
  1069.           fragment.appendChild(li);
  1070.         }
  1071.         removeChildren(element);
  1072.         element.appendChild(fragment);
  1073.       }
  1074.  
  1075.       switch (componentType) {
  1076.         case 0:
  1077.           redrawList(this, 'Controller', document.getElementById(
  1078.               'audio-input-controller-list'));
  1079.           break;
  1080.         case 1:
  1081.           redrawList(this, 'Controller', document.getElementById(
  1082.               'audio-output-controller-list'));
  1083.           break;
  1084.         case 2:
  1085.           redrawList(this, 'Stream', document.getElementById(
  1086.               'audio-output-stream-list'));
  1087.           break;
  1088.         default:
  1089.           break;
  1090.       }
  1091.     },
  1092.  
  1093.     selectAudioComponent_: function(
  1094.           componentType, componentId, componentData, friendlyName) {
  1095.       this.selectedPlayer = null;
  1096.       this.selectedAudioComponentType = componentType;
  1097.       this.selectedAudioComponentId = componentId;
  1098.       this.selectedAudioCompontentData = componentData;
  1099.       this.drawProperties_(componentData);
  1100.       removeChildren(this.logTable);
  1101.       removeChildren(this.graphElement);
  1102.  
  1103.       removeChildren(this.propertyName);
  1104.       this.propertyName.appendChild(document.createTextNode(friendlyName));
  1105.     },
  1106.  
  1107.     redrawPlayerList_: function(players) {
  1108.       var fragment = document.createDocumentFragment();
  1109.       for (id in players) {
  1110.         var player = players[id];
  1111.         var usableName = player.properties.name ||
  1112.             player.properties.url ||
  1113.             'Player ' + player.id;
  1114.  
  1115.         var li = document.createElement('li');
  1116.         li.appendChild(createButton(
  1117.             usableName, this.selectPlayer_.bind(this, player)));
  1118.         fragment.appendChild(li);
  1119.       }
  1120.       removeChildren(this.playerListElement);
  1121.       this.playerListElement.appendChild(fragment);
  1122.     },
  1123.  
  1124.     selectPlayer_: function(player) {
  1125.       this.selectedPlayer = player;
  1126.       this.selectedPlayerLogIndex = 0;
  1127.       this.selectedAudioComponentType = null;
  1128.       this.selectedAudioComponentId = null;
  1129.       this.selectedAudioCompontentData = null;
  1130.       this.drawProperties_(player.properties);
  1131.  
  1132.       removeChildren(this.logTable);
  1133.       removeChildren(this.graphElement);
  1134.       this.drawLog_();
  1135.       this.drawGraphs_();
  1136.       removeChildren(this.propertyName);
  1137.       this.propertyName.appendChild(document.createTextNode('Player'));
  1138.     },
  1139.  
  1140.     drawProperties_: function(propertyMap) {
  1141.       removeChildren(this.propertiesTable);
  1142.       var sortedKeys = Object.keys(propertyMap).sort();
  1143.       for (var i = 0; i < sortedKeys.length; ++i) {
  1144.         var key = sortedKeys[i];
  1145.         if (this.hiddenKeys.indexOf(key) >= 0)
  1146.           continue;
  1147.  
  1148.         var value = propertyMap[key];
  1149.         var row = this.propertiesTable.insertRow(-1);
  1150.         var keyCell = row.insertCell(-1);
  1151.         var valueCell = row.insertCell(-1);
  1152.  
  1153.         keyCell.appendChild(document.createTextNode(key));
  1154.         valueCell.appendChild(document.createTextNode(value));
  1155.       }
  1156.     },
  1157.  
  1158.     appendEventToLog_: function(event) {
  1159.       if (this.filterFunction(event.key)) {
  1160.         var row = this.logTable.insertRow(-1);
  1161.  
  1162.         var timestampCell = row.insertCell(-1);
  1163.         timestampCell.classList.add('timestamp');
  1164.         timestampCell.appendChild(document.createTextNode(
  1165.             util.millisecondsToString(event.time)));
  1166.         row.insertCell(-1).appendChild(document.createTextNode(event.key));
  1167.         row.insertCell(-1).appendChild(document.createTextNode(event.value));
  1168.       }
  1169.     },
  1170.  
  1171.     drawLog_: function() {
  1172.       var toDraw = this.selectedPlayer.allEvents.slice(
  1173.           this.selectedPlayerLogIndex);
  1174.       toDraw.forEach(this.appendEventToLog_.bind(this));
  1175.       this.selectedPlayerLogIndex = this.selectedPlayer.allEvents.length;
  1176.     },
  1177.  
  1178.     drawGraphs_: function() {
  1179.       function addToGraphs(name, graph, graphElement) {
  1180.         var li = document.createElement('li');
  1181.         li.appendChild(graph);
  1182.         li.appendChild(document.createTextNode(name));
  1183.         graphElement.appendChild(li);
  1184.       }
  1185.  
  1186.       var url = this.selectedPlayer.properties.url;
  1187.       if (!url) {
  1188.         return;
  1189.       }
  1190.  
  1191.       var cache = media.cacheForUrl(url);
  1192.  
  1193.       var player = this.selectedPlayer;
  1194.       var props = player.properties;
  1195.  
  1196.       var cacheExists = false;
  1197.       var bufferExists = false;
  1198.  
  1199.       if (props['buffer_start'] !== undefined &&
  1200.           props['buffer_current'] !== undefined &&
  1201.           props['buffer_end'] !== undefined &&
  1202.           props['total_bytes'] !== undefined) {
  1203.         this.drawBufferGraph_(props['buffer_start'],
  1204.                               props['buffer_current'],
  1205.                               props['buffer_end'],
  1206.                               props['total_bytes']);
  1207.         bufferExists = true;
  1208.       }
  1209.  
  1210.       if (cache) {
  1211.         if (player.properties['total_bytes']) {
  1212.           cache.size = Number(player.properties['total_bytes']);
  1213.         }
  1214.         cache.generateDetails();
  1215.         cacheExists = true;
  1216.  
  1217.       }
  1218.  
  1219.       if (!this.graphElement.hasChildNodes()) {
  1220.         if (bufferExists) {
  1221.           addToGraphs('buffer', this.bufferCanvas, this.graphElement);
  1222.         }
  1223.         if (cacheExists) {
  1224.           addToGraphs('cache read', cache.readCanvas, this.graphElement);
  1225.           addToGraphs('cache write', cache.writeCanvas, this.graphElement);
  1226.         }
  1227.       }
  1228.     },
  1229.  
  1230.     drawBufferGraph_: function(start, current, end, size) {
  1231.       var ctx = this.bufferCanvas.getContext('2d');
  1232.       var width = this.bufferCanvas.width;
  1233.       var height = this.bufferCanvas.height;
  1234.       ctx.fillStyle = '#aaa';
  1235.       ctx.fillRect(0, 0, width, height);
  1236.  
  1237.       var scale_factor = width / size;
  1238.       var left = start * scale_factor;
  1239.       var middle = current * scale_factor;
  1240.       var right = end * scale_factor;
  1241.  
  1242.       ctx.fillStyle = '#a0a';
  1243.       ctx.fillRect(left, 0, middle - left, height);
  1244.       ctx.fillStyle = '#aa0';
  1245.       ctx.fillRect(middle, 0, right - middle, height);
  1246.     },
  1247.  
  1248.     copyToClipboard_: function() {
  1249.       var properties = this.selectedAudioCompontentData ||
  1250.           this.selectedPlayer.properties || false;
  1251.       if (!properties) {
  1252.         return;
  1253.       }
  1254.       var stringBuffer = [];
  1255.  
  1256.       for (var key in properties) {
  1257.         var value = properties[key];
  1258.         stringBuffer.push(key.toString());
  1259.         stringBuffer.push(': ');
  1260.         stringBuffer.push(value.toString());
  1261.         stringBuffer.push('\n');
  1262.       }
  1263.  
  1264.       this.clipboardTextarea.value = stringBuffer.join('');
  1265.       this.clipboardTextarea.classList.remove('hiddenClipboard');
  1266.       this.clipboardTextarea.focus();
  1267.       this.clipboardTextarea.select();
  1268.  
  1269.       // Hide the clipboard element when it loses focus.
  1270.       this.clipboardTextarea.onblur = function(event) {
  1271.         setTimeout(function(element) {
  1272.           event.target.classList.add('hiddenClipboard');
  1273.         }, 0);
  1274.       };
  1275.     },
  1276.  
  1277.     onTextChange_: function(event) {
  1278.       var text = this.filterText.value.toLowerCase();
  1279.       var parts = text.split(',').map(function(part) {
  1280.         return part.trim();
  1281.       }).filter(function(part) {
  1282.         return part.trim().length > 0;
  1283.       });
  1284.  
  1285.       this.filterFunction = function(text) {
  1286.         text = text.toLowerCase();
  1287.         return parts.length === 0 || parts.some(function(part) {
  1288.           return text.indexOf(part) != -1;
  1289.         });
  1290.       };
  1291.  
  1292.       if (this.selectedPlayer) {
  1293.         removeChildren(this.logTable);
  1294.         this.selectedPlayerLogIndex = 0;
  1295.         this.drawLog_();
  1296.       }
  1297.     },
  1298.   };
  1299.  
  1300.   return ClientRenderer;
  1301. })();
  1302.  
  1303.  
  1304. media.initialize(new Manager(new ClientRenderer()));
  1305.